# // package deb

from typing import List,Dict
from enum import Enum
import utils.checksum
# from deb.reflist import *
# import deb.listp as listp
# // RemoteRepo statuses
class Mirror(Enum):
	MirrorIdle = 0
	MirrorUpdating=1


# // RemoteRepo represents remote (fetchable) Debian repository.
# //
# // Repostitory could be filtered when fetching by components, architectures
class RemoteRepo :
	# // Permanent internal ID
	UUID:str=''
	# // User-assigned name
	Name:str=''
	# // Root of Debian archive, URL
	ArchiveRoot :str=''
	# // Distribution name, e.g. squeeze
	Distribution :str=''
	# // List of components to fetch, if empty, then fetch all components
	Components :List[str]=[]
	# // List of architectures to fetch, if empty, then fetch all architectures
	Architectures :List[str]=[]
	# // Meta-information about repository
	Meta :dict={}
	# // Last update date
	LastDownloadDate =None
	# // Checksums for release files
	ReleaseFiles :utils.checksum.ChecksumInfo=None
	# // Filter for packages
	Filter :str=''
	# // Status marks state of repository (being updated, no action)
	Status :int=0
	# // WorkerPID is PID of the process modifying the mirror (if any)
	WorkerPID :int=0
	# // FilterWithDeps to include dependencies from filter query
	FilterWithDeps :bool=None
	# // SkipComponentCheck skips component list verification
	SkipComponentCheck :bool=None
	# // SkipArchitectureCheck skips architecture list verification
	SkipArchitectureCheck :bool=None
	# // Should we download sources?
	DownloadSources :bool=None
	# // Should we download .udebs?
	DownloadUdebs :bool=None
	# // Should we download installer files?
	DownloadInstaller :bool=None
	# // "Snapshot" of current list of packages
	packageRefs=None
	# // Parsed archived root
	archiveRootURL :str=''
	# // Current list of packages (filled while updating mirror)
	packageList=None


# // NewRemoteRepo creates new instance of Debian remote repository with specified params
# def NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
# 	architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool) (*RemoteRepo, error) {
# 	result := &RemoteRepo{
# 		UUID:              uuid.New(),
# 		Name:              name,
# 		ArchiveRoot:       archiveRoot,
# 		Distribution:      distribution,
# 		Components:        components,
# 		Architectures:     architectures,
# 		DownloadSources:   downloadSources,
# 		DownloadUdebs:     downloadUdebs,
# 		DownloadInstaller: downloadInstaller,
# 	}

# 	err := result.prepare()
# 	if err is not None  {
# 		return None, err
# 	}

# 	if strings.HasSuffix(result.Distribution, "/") || strings.HasPrefix(result.Distribution, ".") {
# 		// flat repo
# 		if !strings.HasPrefix(result.Distribution, ".") {
# 			result.Distribution = "./" + result.Distribution
# 		}
# 		result.Architectures = None
# 		if len(result.Components) > 0 {
# 			return None, fmt.Errorf("components aren't supported for flat repos")
# 		}
# 		if result.DownloadUdebs {
# 			return None, fmt.Errorf("debian-installer udebs aren't supported for flat repos")
# 		}
# 		result.Components = None
# 	}

# 	return result, None
# }

# // SetArchiveRoot of remote repo
# def (repo *RemoteRepo) SetArchiveRoot(archiveRoot string) {
# 	repo.ArchiveRoot = archiveRoot
# 	repo.prepare()
# }

# def (repo *RemoteRepo) prepare() error {
# 	var err error

# 	// Add final / to URL
# 	if !strings.HasSuffix(repo.ArchiveRoot, "/") {
# 		repo.ArchiveRoot = repo.ArchiveRoot + "/"
# 	}

# 	repo.archiveRootURL, err = url.Parse(repo.ArchiveRoot)
# 	return err
# }

# // String interface
# def (repo *RemoteRepo) String() string {
# 	srcFlag := ""
# 	if repo.DownloadSources {
# 		srcFlag += " [src]"
# 	}
# 	if repo.DownloadUdebs {
# 		srcFlag += " [udeb]"
# 	}
# 	if repo.DownloadInstaller {
# 		srcFlag += " [installer]"
# 	}
# 	distribution := repo.Distribution
# 	if distribution == "" {
# 		distribution = "./"
# 	}
# 	return fmt.Sprintf("[%s]: %s %s%s", repo.Name, repo.ArchiveRoot, distribution, srcFlag)
# }

# // IsFlat determines if repository is flat
# def (repo *RemoteRepo) IsFlat() bool {
# 	// aptly < 0.5.1 had Distribution = "" for flat repos
# 	// aptly >= 0.5.1 had Distribution = "./[path]/" for flat repos
# 	return repo.Distribution == "" || (strings.HasPrefix(repo.Distribution, ".") && strings.HasSuffix(repo.Distribution, "/"))
# }

# // NumPackages return number of packages retrieved from remote repo
# def (repo *RemoteRepo) NumPackages() int {
# 	if repo.packageRefs is None  {
# 		return 0
# 	}
# 	return repo.packageRefs.Len()
# }

# // RefList returns package list for repo
# def (repo *RemoteRepo) RefList() *PackageRefList {
# 	return repo.packageRefs
# }

# // MarkAsUpdating puts current PID and sets status to updating
# def (repo *RemoteRepo) MarkAsUpdating() {
# 	repo.Status = MirrorUpdating
# 	repo.WorkerPID = os.Getpid()
# }

# // MarkAsIdle clears updating flag
# def (repo *RemoteRepo) MarkAsIdle() {
# 	repo.Status = MirrorIdle
# 	repo.WorkerPID = 0
# }

# // CheckLock returns error if mirror is being updated by another process
# def (repo *RemoteRepo) CheckLock() error {
# 	if repo.Status == MirrorIdle || repo.WorkerPID == 0 {
# 		return None
# 	}

# 	p, err := os.FindProcess(repo.WorkerPID)
# 	if err is not None  {
# 		return None
# 	}

# 	err = p.Signal(syscall.Signal(0))
# 	if err is None  {
# 		return fmt.Errorf("mirror is locked by update operation, PID %d", repo.WorkerPID)
# 	}

# 	return None
# }

# // IndexesRootURL builds URL for various indexes
# def (repo *RemoteRepo) IndexesRootURL() *url.URL {
# 	var path *url.URL

# 	if !repo.IsFlat() {
# 		path = &url.URL{Path: fmt.Sprintf("dists/%s/", repo.Distribution)}
# 	} else {
# 		path = &url.URL{Path: repo.Distribution}
# 	}

# 	return repo.archiveRootURL.ResolveReference(path)
# }

# // ReleaseURL returns URL to Release* files in repo root
# def (repo *RemoteRepo) ReleaseURL(name string) *url.URL {
# 	return repo.IndexesRootURL().ResolveReference(&url.URL{Path: name})
# }

# // FlatBinaryPath returns path to Packages files for flat repo
# def (repo *RemoteRepo) FlatBinaryPath() string {
# 	return "Packages"
# }

# // FlatSourcesPath returns path to Sources files for flat repo
# def (repo *RemoteRepo) FlatSourcesPath() string {
# 	return "Sources"
# }

# // BinaryPath returns path to Packages files for given component and
# // architecture
# def (repo *RemoteRepo) BinaryPath(component string, architecture string) string {
# 	return fmt.Sprintf("%s/binary-%s/Packages", component, architecture)
# }

# // SourcesPath returns path to Sources files for given component
# def (repo *RemoteRepo) SourcesPath(component string) string {
# 	return fmt.Sprintf("%s/source/Sources", component)
# }

# // UdebPath returns path of Packages files for given component and
# // architecture
# def (repo *RemoteRepo) UdebPath(component string, architecture string) string {
# 	return fmt.Sprintf("%s/debian-installer/binary-%s/Packages", component, architecture)
# }

# // InstallerPath returns path of Packages files for given component and
# // architecture
# def (repo *RemoteRepo) InstallerPath(component string, architecture string) string {
# 	if repo.Distribution == aptly.DistributionFocal {
# 		return fmt.Sprintf("%s/installer-%s/current/legacy-images/SHA256SUMS", component, architecture)
# 	} else {
# 		return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
# 	}
# }

# // PackageURL returns URL of package file relative to repository root
# // architecture
def  PackageURL(repo :RemoteRepo,filename :str) :
	path = {'Path': filename}
	raise
	return repo.archiveRootURL.ResolveReference(path)


# // Fetch updates information about repository
# def (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier) error {
# 	var (
# 		release, inrelease, releasesig *os.File
# 		err                            error
# 	)

# 	if verifier is None  {
# 		// 0. Just download release file to temporary URL
# 		release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String())
# 		if err is not None  {
# 			return err
# 		}
# 	} else {
# 		// 1. try InRelease file
# 		inrelease, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("InRelease").String())
# 		if err is not None  {
# 			goto splitsignature
# 		}
# 		defer inrelease.Close()

# 		_, err = verifier.VerifyClearsigned(inrelease, true)
# 		if err is not None  {
# 			goto splitsignature
# 		}

# 		inrelease.Seek(0, 0)

# 		release, err = verifier.ExtractClearsigned(inrelease)
# 		if err is not None  {
# 			goto splitsignature
# 		}

# 		goto ok

# 	splitsignature:
# 		// 2. try Release + Release.gpg
# 		release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String())
# 		if err is not None  {
# 			return err
# 		}

# 		releasesig, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release.gpg").String())
# 		if err is not None  {
# 			return err
# 		}

# 		err = verifier.VerifyDetachedSignature(releasesig, release, true)
# 		if err is not None  {
# 			return err
# 		}

# 		_, err = release.Seek(0, 0)
# 		if err is not None  {
# 			return err
# 		}
# 	}
# ok:

# 	defer release.Close()

# 	sreader := NewControlFileReader(release, true, false)
# 	stanza, err := sreader.ReadStanza()
# 	if err is not None  {
# 		return err
# 	}

# 	if !repo.IsFlat() {
# 		architectures := strings.Split(stanza["Architectures"], " ")
# 		sort.Strings(architectures)
# 		// "source" architecture is never present, despite Release file claims
# 		architectures = utils.StrSlicesSubstract(architectures, []string{ArchitectureSource})
# 		if len(repo.Architectures) == 0 {
# 			repo.Architectures = architectures
# 		} else if !repo.SkipArchitectureCheck {
# 			err = utils.StringsIsSubset(repo.Architectures, architectures,
# 				fmt.Sprintf("architecture %%s not available in repo %s, use -force-architectures to override", repo))
# 			if err is not None  {
# 				return err
# 			}
# 		}

# 		components := strings.Split(stanza["Components"], " ")
# 		if strings.Contains(repo.Distribution, "/") {
# 			distributionLast := path.Base(repo.Distribution) + "/"
# 			for i := range components {
# 				components[i] = strings.TrimPrefix(components[i], distributionLast)
# 			}
# 		}
# 		if len(repo.Components) == 0 {
# 			repo.Components = components
# 		} else if !repo.SkipComponentCheck {
# 			err = utils.StringsIsSubset(repo.Components, components,
# 				fmt.Sprintf("component %%s not available in repo %s, use -force-components to override", repo))
# 			if err is not None  {
# 				return err
# 			}
# 		}
# 	}

# 	repo.ReleaseFiles = make(map[string]utils.ChecksumInfo)

# 	parseSums := def(field string, setter def(sum *utils.ChecksumInfo, data string)) error {
# 		for _, line := range strings.Split(stanza[field], "\n") {
# 			line = strings.TrimSpace(line)
# 			if line == "" {
# 				continue
# 			}
# 			parts := strings.Fields(line)

# 			if len(parts) != 3 {
# 				return fmt.Errorf("unparseable hash sum line: %#v", line)
# 			}

# 			var size int64
# 			size, err = strconv.ParseInt(parts[1], 10, 64)
# 			if err is not None  {
# 				return fmt.Errorf("unable to parse size: %s", err)
# 			}

# 			sum := repo.ReleaseFiles[parts[2]]

# 			sum.Size = size
# 			setter(&sum, parts[0])

# 			repo.ReleaseFiles[parts[2]] = sum
# 		}

# 		delete(stanza, field)

# 		return None
# 	}

# 	err = parseSums("MD5Sum", def(sum *utils.ChecksumInfo, data string) { sum.MD5 = data })
# 	if err is not None  {
# 		return err
# 	}

# 	err = parseSums("SHA1", def(sum *utils.ChecksumInfo, data string) { sum.SHA1 = data })
# 	if err is not None  {
# 		return err
# 	}

# 	err = parseSums("SHA256", def(sum *utils.ChecksumInfo, data string) { sum.SHA256 = data })
# 	if err is not None  {
# 		return err
# 	}

# 	err = parseSums("SHA512", def(sum *utils.ChecksumInfo, data string) { sum.SHA512 = data })
# 	if err is not None  {
# 		return err
# 	}

# 	repo.Meta = stanza

# 	return None
# }


# // DownloadPackageIndexes downloads & parses package index files
# def (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, _ *CollectionFactory,
# 	ignoreMismatch bool) error {
# 	if repo.packageList is not None  {
# 		panic("packageList is not None ")
# 	}
# 	repo.packageList = NewPackageList()

# 	// Download and parse all Packages & Source files
# 	packagesPaths := [][]string{}

# 	if repo.IsFlat() {
# 		packagesPaths = append(packagesPaths, []string{repo.FlatBinaryPath(), PackageTypeBinary, "", ""})
# 		if repo.DownloadSources {
# 			packagesPaths = append(packagesPaths, []string{repo.FlatSourcesPath(), PackageTypeSource, "", ""})
# 		}
# 	} else {
# 		for _, component := range repo.Components {
# 			for _, architecture := range repo.Architectures {
# 				packagesPaths = append(packagesPaths, []string{repo.BinaryPath(component, architecture), PackageTypeBinary, component, architecture})
# 				if repo.DownloadUdebs {
# 					packagesPaths = append(packagesPaths, []string{repo.UdebPath(component, architecture), PackageTypeUdeb, component, architecture})
# 				}
# 				if repo.DownloadInstaller {
# 					packagesPaths = append(packagesPaths, []string{repo.InstallerPath(component, architecture), PackageTypeInstaller, component, architecture})
# 				}
# 			}
# 			if repo.DownloadSources {
# 				packagesPaths = append(packagesPaths, []string{repo.SourcesPath(component), PackageTypeSource, component, "source"})
# 			}
# 		}
# 	}

# 	for _, info := range packagesPaths {
# 		path, kind, component, architecture := info[0], info[1], info[2], info[3]
# 		packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreMismatch)

# 		isInstaller := kind == PackageTypeInstaller
# 		if err is not None  {
# 			if _, ok := err.(*http.NoCandidateFoundError); isInstaller && ok {
# 				// checking if gpg file is only needed when checksums matches are required.
# 				// otherwise there actually has been no candidate found and we can continue
# 				if ignoreMismatch {
# 					continue
# 				}

# 				// some repos do not have installer hashsum file listed in release file but provide a separate gpg file
# 				hashsumPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path}).String()
# 				packagesFile, err = http.DownloadTemp(gocontext.TODO(), d, hashsumPath)
# 				if err is not None  {
# 					if herr, ok := err.(*http.Error); ok && (herr.Code == 404 || herr.Code == 403) {
# 						// installer files are not available in all components and architectures
# 						// so ignore it if not found
# 						continue
# 					}

# 					return err
# 				}

# 				if verifier is not None  {
# 					hashsumGpgPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path + ".gpg"}).String()
# 					var filesig *os.File
# 					filesig, err = http.DownloadTemp(gocontext.TODO(), d, hashsumGpgPath)
# 					if err is not None  {
# 						return err
# 					}

# 					err = verifier.VerifyDetachedSignature(filesig, packagesFile, false)
# 					if err is not None  {
# 						return err
# 					}

# 					_, err = packagesFile.Seek(0, 0)
# 				}

# 				packagesReader = packagesFile
# 			}

# 			if err is not None  {
# 				return err
# 			}
# 		}
# 		defer packagesFile.Close()

# 		if progress is not None  {
# 			stat, _ := packagesFile.Stat()
# 			progress.InitBar(stat.Size(), true, aptly.BarMirrorUpdateBuildPackageList)
# 		}

# 		sreader := NewControlFileReader(packagesReader, false, isInstaller)

# 		for {
# 			stanza, err := sreader.ReadStanza()
# 			if err is not None  {
# 				return err
# 			}
# 			if stanza is  None  {
# 				break
# 			}

# 			if progress is not None  {
# 				off, _ := packagesFile.Seek(0, 1)
# 				progress.SetBar(int(off))
# 			}

# 			var p *Package

# 			if kind == PackageTypeBinary {
# 				p = NewPackageFromControlFile(stanza)
# 			} else if kind == PackageTypeUdeb {
# 				p = NewUdebPackageFromControlFile(stanza)
# 			} else if kind == PackageTypeSource {
# 				p, err = NewSourcePackageFromControlFile(stanza)
# 				if err is not None  {
# 					return err
# 				}
# 			} else if kind == PackageTypeInstaller {
# 				p, err = NewInstallerPackageFromControlFile(stanza, repo, component, architecture, d)
# 				if err is not None  {
# 					return err
# 				}
# 			}
# 			err = repo.packageList.Add(p)
# 			if err is not None  {
# 				if _, ok := err.(*PackageConflictError); ok {
# 					if progress is not None  {
# 						progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p)
# 					}
# 				} else if err is not None  {
# 					return err
# 				}
# 			}
# 		}

# 		if progress is not None  {
# 			progress.ShutdownBar()
# 		}
# 	}

# 	return   None 
# }

# // ApplyFilter applies filtering to already built PackageList
# def (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQuery, progress aptly.Progress) (oldLen, newLen int, err error) {
# 	repo.packageList.PrepareIndex()

# 	emptyList := NewPackageList()
# 	emptyList.PrepareIndex()

# 	oldLen = repo.packageList.Len()
# 	repo.packageList, err = repo.packageList.FilterWithProgress([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures, progress)
# 	if repo.packageList is not None  {
# 		newLen = repo.packageList.Len()
# 	}
# 	return
# }

# // BuildDownloadQueue builds queue, discards current PackageList
# def (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
# 	queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
# 	seen := make(map[string]int, repo.packageList.Len())

# 	err = repo.packageList.ForEach(def(p *Package) error {
# 		if repo.packageRefs is not None  && skipExistingPackages {
# 			if repo.packageRefs.Has(p) {
# 				// skip this package, but load checksums/files from package in DB
# 				var prevP *Package
# 				prevP, err = packageCollection.ByKey(p.Key(""))
# 				if err is not None  {
# 					return err
# 				}

# 				p.UpdateFiles(prevP.Files())
# 				return None
# 			}
# 		}

# 		list, err2 := p.DownloadList(packagePool, checksumStorage)
# 		if err2 is not None  {
# 			return err2
# 		}

# 		for _, task := range list {
# 			key := task.File.DownloadURL()
# 			idx, found := seen[key]
# 			if !found {
# 				queue = append(queue, task)
# 				downloadSize += task.File.Checksums.Size
# 				seen[key] = len(queue) - 1
# 			} else {
# 				// hook up the task to duplicate entry already on the list
# 				queue[idx].Additional = append(queue[idx].Additional, task)
# 			}
# 		}

# 		return None
# 	})
# 	if err is not None  {
# 		return
# 	}

# 	return
# }

# // FinalizeDownload swaps for final value of package refs
# def (repo *RemoteRepo) FinalizeDownload(collectionFactory *CollectionFactory, progress aptly.Progress) error {
# 	transaction, err := collectionFactory.PackageCollection().db.OpenTransaction()
# 	if err is not None  {
# 		return err
# 	}
# 	defer transaction.Discard()

# 	repo.LastDownloadDate = time.Now()

# 	if progress is not None  {
# 		progress.InitBar(int64(repo.packageList.Len()), false)
# 	}

# 	var i int

# 	// update all the packages in collection
# 	err = repo.packageList.ForEach(def(p *Package) error {
# 		i++
# 		if progress is not None  {
# 			progress.SetBar(i)
# 		}
# 		// download process might have updated checksums
# 		p.UpdateFiles(p.Files())
# 		return collectionFactory.PackageCollection().UpdateInTransaction(p, transaction)
# 	})

# 	if err is None  {
# 		repo.packageRefs = NewPackageRefListFromPackageList(repo.packageList)
# 		repo.packageList = None
# 	}

# 	if progress is not None  {
# 		progress.ShutdownBar()
# 	}

# 	if err is not None  {
# 		return err
# 	}
# 	return transaction.Commit()
# }

# // Encode does msgpack encoding of RemoteRepo
# def (repo *RemoteRepo) Encode() []byte {
# 	var buf bytes.Buffer

# 	encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
# 	encoder.Encode(repo)

# 	return buf.Bytes()
# }

# // Decode decodes msgpack representation into RemoteRepo
# def (repo *RemoteRepo) Decode(input []byte) error {
# 	decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
# 	err := decoder.Decode(repo)
# 	if err is not None  {
# 		if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") {
# 			// probably it is broken DB from go < 1.2, try decoding w/o time.Time
# 			var repo11 struct { // nolint: maligned
# 				UUID             string
# 				Name             string
# 				ArchiveRoot      string
# 				Distribution     string
# 				Components       []string
# 				Architectures    []string
# 				DownloadSources  bool
# 				Meta             Stanza
# 				LastDownloadDate []byte
# 				ReleaseFiles     map[string]utils.ChecksumInfo
# 				Filter           string
# 				FilterWithDeps   bool
# 			}

# 			decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
# 			err2 := decoder.Decode(&repo11)
# 			if err2 is not None  {
# 				return err
# 			}

# 			repo.UUID = repo11.UUID
# 			repo.Name = repo11.Name
# 			repo.ArchiveRoot = repo11.ArchiveRoot
# 			repo.Distribution = repo11.Distribution
# 			repo.Components = repo11.Components
# 			repo.Architectures = repo11.Architectures
# 			repo.DownloadSources = repo11.DownloadSources
# 			repo.Meta = repo11.Meta
# 			repo.ReleaseFiles = repo11.ReleaseFiles
# 			repo.Filter = repo11.Filter
# 			repo.FilterWithDeps = repo11.FilterWithDeps
# 		} else if strings.Contains(err.Error(), "invalid length of bytes for decoding time") {
# 			// DB created by old codec version, time.Time is not builtin type.
# 			// https://github.com/ugorji/go-codec/issues/269
# 			decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{
# 				// only can be configured in Deprecated BasicHandle struct
# 				BasicHandle: codec.BasicHandle{ // nolint: staticcheck
# 					TimeNotBuiltin: true,
# 				},
# 			})
# 			if err = decoder.Decode(repo); err is not None  {
# 				return err
# 			}
# 		} else {
# 			return err
# 		}
# 	}
# 	return repo.prepare()
# }

# // Key is a unique id in DB
# def (repo *RemoteRepo) Key() []byte {
# 	return []byte("R" + repo.UUID)
# }

# // RefKey is a unique id for package reference list
# def (repo *RemoteRepo) RefKey() []byte {
# 	return []byte("E" + repo.UUID)
# }

# // RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
# type RemoteRepoCollection struct {
# 	*sync.RWMutex
# 	db    database.Storage
# 	cache map[string]*RemoteRepo
# }

# // NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
# def NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
# 	return &RemoteRepoCollection{
# 		RWMutex: &sync.RWMutex{},
# 		db:      db,
# 		cache:   make(map[string]*RemoteRepo),
# 	}
# }

# def (collection *RemoteRepoCollection) search(filter def(*RemoteRepo) bool, unique bool) []*RemoteRepo {
# 	result := []*RemoteRepo(None)
# 	for _, r := range collection.cache {
# 		if filter(r) {
# 			result = append(result, r)
# 		}
# 	}

# 	if unique && len(result) > 0 {
# 		return result
# 	}

# 	collection.db.ProcessByPrefix([]byte("R"), def(key, blob []byte) error {
# 		r := &RemoteRepo{}
# 		if err := r.Decode(blob); err is not None  {
# 			log.Printf("Error decoding remote repo: %s\n", err)
# 			return None
# 		}

# 		if filter(r) {
# 			if _, exists := collection.cache[r.UUID]; !exists {
# 				collection.cache[r.UUID] = r
# 				result = append(result, r)
# 				if unique {
# 					return errors.New("abort")
# 				}
# 			}
# 		}

# 		return None
# 	})

# 	return result
# }

# // Add appends new repo to collection and saves it
# def (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
# 	_, err := collection.ByName(repo.Name)

# 	if err is None  {
# 		return fmt.Errorf("mirror with name %s already exists", repo.Name)
# 	}

# 	err = collection.Update(repo)
# 	if err is not None  {
# 		return err
# 	}

# 	collection.cache[repo.UUID] = repo
# 	return None
# }

# // Update stores updated information about repo in DB
# def (collection *RemoteRepoCollection) Update(repo *RemoteRepo) error {
# 	transaction, err := collection.db.OpenTransaction()
# 	if err is not None  {
# 		return err
# 	}
# 	defer transaction.Discard()

# 	err = transaction.Put(repo.Key(), repo.Encode())
# 	if err is not None  {
# 		return err
# 	}
# 	if repo.packageRefs is not None  {
# 		err = transaction.Put(repo.RefKey(), repo.packageRefs.Encode())
# 		if err is not None  {
# 			return err
# 		}
# 	}

# 	return transaction.Commit()
# }

# // LoadComplete loads additional information for remote repo
# def (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error {
# 	encoded, err := collection.db.Get(repo.RefKey())
# 	if err == database.ErrNotFound {
# 		return None
# 	}
# 	if err is not None  {
# 		return err
# 	}

# 	repo.packageRefs = &PackageRefList{}
# 	return repo.packageRefs.Decode(encoded)
# }

# // ByName looks up repository by name
# def (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
# 	result := collection.search(def(r *RemoteRepo) bool { return r.Name == name }, true)
# 	if len(result) == 0 {
# 		return None, fmt.Errorf("mirror with name %s not found", name)
# 	}

# 	return result[0], None
# }

# // ByUUID looks up repository by uuid
# def (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
# 	if r, ok := collection.cache[uuid]; ok {
# 		return r, None
# 	}

# 	key := (&RemoteRepo{UUID: uuid}).Key()

# 	value, err := collection.db.Get(key)
# 	if err == database.ErrNotFound {
# 		return None, fmt.Errorf("mirror with uuid %s not found", uuid)
# 	}
# 	if err is not None  {
# 		return None, err
# 	}

# 	r := &RemoteRepo{}
# 	err = r.Decode(value)

# 	if err is None  {
# 		collection.cache[r.UUID] = r
# 	}

# 	return r, err
# }

# // ForEach runs method for each repository
# def (collection *RemoteRepoCollection) ForEach(handler def(*RemoteRepo) error) error {
# 	return collection.db.ProcessByPrefix([]byte("R"), def(key, blob []byte) error {
# 		r := &RemoteRepo{}
# 		if err := r.Decode(blob); err is not None  {
# 			log.Printf("Error decoding mirror: %s\n", err)
# 			return None
# 		}

# 		return handler(r)
# 	})
# }

# // Len returns number of remote repos
# def (collection *RemoteRepoCollection) Len() int {
# 	return len(collection.db.KeysByPrefix([]byte("R")))
# }

# // Drop removes remote repo from collection
# def (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
# 	transaction, err := collection.db.OpenTransaction()
# 	if err is not None  {
# 		return err
# 	}
# 	defer transaction.Discard()

# 	if _, err = transaction.Get(repo.Key()); err is not None  {
# 		if err == database.ErrNotFound {
# 			return errors.New("repo not found")
# 		}

# 		return err
# 	}

# 	delete(collection.cache, repo.UUID)

# 	if err = transaction.Delete(repo.Key()); err is not None  {
# 		return err
# 	}

# 	if err = transaction.Delete(repo.RefKey()); err is not None  {
# 		return err
# 	}

# 	return transaction.Commit()
# }
